﻿namespace SageKing.Core.Contracts.FilterTypes;
public abstract class DateFieldFilterType : BaseFieldFilterType
{
    private static IEnumerable<SageKingFilterCompareOperator> _supportedCompareOperators = new[]
    {
        SageKingFilterCompareOperator.Equals,
        SageKingFilterCompareOperator.NotEquals,
        SageKingFilterCompareOperator.GreaterThan,
        SageKingFilterCompareOperator.LessThan,
        SageKingFilterCompareOperator.GreaterThanOrEquals,
        SageKingFilterCompareOperator.LessThanOrEquals,
        SageKingFilterCompareOperator.Between
    };

    public DateFieldFilterType()
    {
        SupportedCompareOperators = _supportedCompareOperators;
    }

    protected virtual Expression GetNonNullFilterExpression(SageKingFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
    {
        if (compareOperator == SageKingFilterCompareOperator.Between)
        {
            Expression range0 = Expression.ArrayIndex(rightExpr, Expression.Constant(0));
            Expression range1 = Expression.ArrayIndex(rightExpr, Expression.Constant(1));

            if (range0.Type.IsTypeNullable())
            {
                range0 = Expression.Property(range0, nameof(Nullable<DateTime>.Value));
            }

            if (range1.Type.IsTypeNullable())
            {
                range1 = Expression.Property(range1, nameof(Nullable<DateTime>.Value));
            }

            return Expression.AndAlso(
                       Expression.GreaterThanOrEqual(leftExpr, range0),
                       Expression.LessThanOrEqual(leftExpr, range1)
                   );
        };

        return base.GetFilterExpression(compareOperator, leftExpr, rightExpr);
    }

    public override sealed Expression GetFilterExpression(SageKingFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
    {
        leftExpr = ConvertToActualTypeIfNecessary(leftExpr, rightExpr);
        rightExpr = ConvertToActualTypeIfNecessary(rightExpr, rightExpr);

        switch (compareOperator)
        {
            case SageKingFilterCompareOperator.IsNull:
                return Expression.Equal(leftExpr, rightExpr);

            case SageKingFilterCompareOperator.IsNotNull:
                return Expression.NotEqual(leftExpr, rightExpr);
        }

        if (rightExpr.Type.IsTypeNullable())
        {
            rightExpr = Expression.Property(rightExpr, nameof(Nullable<DateTime>.Value));
        }

        if (leftExpr.Type.IsTypeNullable())
        {
            Expression notNull = Expression.NotEqual(leftExpr, Expression.Constant(null));
            Expression isNull = Expression.Equal(leftExpr, Expression.Constant(null));
            leftExpr = Expression.Property(leftExpr, nameof(Nullable<DateTime>.Value));

            return compareOperator switch
            {
                SageKingFilterCompareOperator.NotEquals => Expression.OrElse(isNull, GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr)),
                _ => Expression.AndAlso(notNull, GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr))
            };
        }

        return GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr);
    }
}

public class DateTimeFieldFilterType : DateFieldFilterType
{
    public DateTimeFieldFilterType()
    {
        SupportedCompareOperators = [.. base.SupportedCompareOperators, SageKingFilterCompareOperator.TheSameDateWith];
    }

    protected override Expression GetNonNullFilterExpression(SageKingFilterCompareOperator compareOperator,
        Expression leftExpr, Expression rightExpr)
    {
        if (compareOperator != SageKingFilterCompareOperator.TheSameDateWith)
        {
            leftExpr = RemoveMilliseconds(leftExpr);
        }

        return compareOperator switch
        {
            SageKingFilterCompareOperator.TheSameDateWith => Expression.Equal(
                Expression.Property(leftExpr, nameof(DateTime.Date)),
                Expression.Property(rightExpr, nameof(DateTime.Date))),
            _ => base.GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr)
        };
    }

    private static Expression RemoveMilliseconds(Expression dateTimeExpression)
    {
        return Expression.Call(dateTimeExpression, typeof(DateTime).GetMethod(nameof(DateTime.AddMilliseconds))!,
            Expression.Convert(
                Expression.Subtract(Expression.Constant(0),
                    Expression.MakeMemberAccess(dateTimeExpression,
                        typeof(DateTime).GetMember(nameof(DateTime.Millisecond)).First())), typeof(double)));
    }
}
